﻿/*
Copyright (c) 2012 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using TeachMe.Application.ComboItems;
using TeachMe.Application.Configuration;
using TeachMe.Application.Data;
using TeachMe.Application.Forms;
using TeachMe.Application.Properties;

namespace TeachMe.Application.Pexeso
{
    public partial class PexesoForm : PseudoModalForm, IBoardCallback
    {
        private static readonly int[] FontSizes = new[] {8, 10, 12, 16, 20, 24, 32};

        private readonly Root _root;
        private PlayerDescriptor _currentPlayer;
        private readonly List<PlayerDescriptor> _players;
        private readonly Dictionary<PlayerDescriptor, PlayerControl> _playersControls;
        private Dictionary _lastDictionary;
        private int _lastBoardSize;
        private int _lastNumberOfPlayers;
        private bool _ignoreGameSetupChanges;
        private bool _gameHasStarted;
        private FormWindowState _lastWindowState;

        public PexesoForm()
        {
            InitializeComponent();
        }

        public PexesoForm(Root root)
        {
            _root = root;
            _players = new List<PlayerDescriptor>();
            _playersControls = new Dictionary<PlayerDescriptor, PlayerControl>();

            InitializeComponent();

            PrepareContent();
            GeneratePlayers();

            WindowProportions.Lock(this, _root.MachineSettings);
            
            _root.UserSettings.CheckOut();
            _root.Suspender.Suspend();

            pexesoBoard.SetCallback(this);
        }

        private static int MaxPlayersCount
        {
            get { return 4; }
        }

        private int PlayersCount
        {
            get { return ComboBoxItem<int>.GetSelection(cbPlayers); }
            set { ComboBoxItem<int>.Select(cbPlayers, value, (first, second) => first == second); }
        }

        private int BoardSize
        {
            get { return ComboBoxItem<int>.GetSelection(cbBoardSize); }
            set
            {
                if (BoardSizes.Contains(value))
                {
                    ComboBoxItem<int>.Select(cbBoardSize, value, (first, second) => first == second);
                }
                else
                {
                    ComboBoxItem<int>.Select(cbBoardSize, BoardSizes.First(), (first, second) => first == second);
                }
            }
        }

        private int FontSize
        {
            get { return ComboBoxItem<int>.GetSelection(cbFontSize); }
            set { ComboBoxItem<int>.Select(cbFontSize, value, (first, second) => first == second); }
        }

        private static IEnumerable<int> BoardSizes
        {
            get
            {
                for (int idx = 1; idx <= 64; idx++)
                {
                    if (idx % 2 == 0 && Math.Sqrt(idx).Equals(Math.Round(Math.Sqrt(idx))))
                    {
                        yield return idx;
                    }
                }
            }
        }

        private Dictionary Dictionary
        {
            get
            {
                return cbDictionary.SelectedItem != null ? ComboBoxItem<Dictionary>.GetSelection(cbDictionary) : null;
            }
            set
            {
                if (value != null)
                {
                    ComboBoxItem<Dictionary>.Select(cbDictionary, value);
                }
                else
                {
                    cbDictionary.SelectedItem = null;
                }
            }
        }

        private void PrepareContent()
        {
            // players
            for (int count = 1; count <= MaxPlayersCount; count++)
            {
                cbPlayers.Items.Add(new ComboBoxItem<int>(count));
            }
            cbPlayers.SelectedIndexChanged += OnPlayersCountChanged;

            // board size
            foreach (int boardSize in BoardSizes)
            {
                cbBoardSize.Items.Add(new ComboBoxItem<int>(boardSize));
            }
            cbBoardSize.SelectedIndexChanged += OnBoardSizeChanged;
            
            // dicts
            foreach (Dictionary dict in _root.Dictionaries)
            {
                cbDictionary.Items.Add(new ComboBoxItem<Dictionary>(dict, String.Format("{0} ({1})", dict.Entries.Name, EnumCaptionResolver.Translate(dict, EntryTextDirection.FromTo))));
            }
            cbDictionary.SelectedIndexChanged += OnSelectedDictionaryChanged;

            // board size
            foreach (int fontSize in FontSizes)
            {
                cbFontSize.Items.Add(new ComboBoxItem<int>(fontSize));
            }
            cbFontSize.SelectedIndexChanged += OnFontSizeChanged;

            //

            try
            {
                _ignoreGameSetupChanges = true;

                _lastBoardSize = BoardSize = _root.UserSettings.PexesoSettings.BoardSize.Value;
                _lastNumberOfPlayers = PlayersCount = _root.UserSettings.PexesoSettings.PlayersCount.Value;
                
                FontSize = _root.UserSettings.PexesoSettings.FontSize.Value;
                cbHighlight.Checked = pexesoBoard.HighlightFirstWord = _root.UserSettings.PexesoSettings.HighlightFirstWord.Value;

                Dictionary dictionary = GetDefaultDictionary();

                if (dictionary != null)
                {
                    _lastDictionary = Dictionary = dictionary;
                }
            }
            finally
            {
                _ignoreGameSetupChanges = false;
            }
        }

        private Dictionary GetDefaultDictionary()
        {
            Guid defaultDictionary = _root.UserSettings.PexesoSettings.Dictionary.Value;
            if (defaultDictionary != Guid.Empty)
            {
                return _root.Dictionaries.Where(x => x.Entries.Id == defaultDictionary).FirstOrDefault(x => x.Entries.Items.Count >= BoardSize);
            }
            return _root.Dictionaries.FirstOrDefault(x => x.Entries.Items.Count >= BoardSize);
        }

        private void ResetScore()
        {
            foreach (PlayerDescriptor playerDescriptor in _players)
            {
                playerDescriptor.Score = 0;

                _playersControls[playerDescriptor].Score = 0;
            }
        }

        private void InitializeBoard()
        {
            pexesoBoard.Enabled = Dictionary != null;
            
            if (Dictionary == null)
            {
                return;
            }

            int axisSize = (int) Math.Sqrt(BoardSize);
            List<Pexes> input = new List<Pexes>();

            Random rnd = new Random();
            IEnumerable<Entry> entries = Dictionary.Entries.Items.Values.OrderBy(item => rnd.Next());

            for (int idx = 0; idx < (axisSize * axisSize / 2); idx++)
            {
                Entry entry = entries.ElementAt(idx);

                input.Add(new Pexes { Word = entry.From, Parent = entry, First = true });
                input.Add(new Pexes { Word = entry.To, Parent = entry });
            }

            pexesoBoard.Load(input, axisSize, FontSize);
        }

        public void PexesesRevealed(Pexes first, Pexes second)
        {
            _currentPlayer.Score += 1;
            _playersControls[_currentPlayer].Score = _currentPlayer.Score;

            ShowInfoCard(String.Format("{0}\n\n{1}", first.Word, second.Word), Color.LavenderBlush);
        }

        public void EndGame()
        {
            EndGame(true);
        }

        public bool Equal(Pexes first, Pexes second)
        {
            return first.Parent == second.Parent;
        }

        public void SwitchPlayer()
        {
            if (_currentPlayer == null)
            {
                _currentPlayer = _players.First();
            }
            else
            {
                int index = _players.IndexOf(_currentPlayer) + 1;
                _currentPlayer = index == _players.Count(x => x.Visible) ? _players[0] : _players[index];
            }

            if (_players.Count(x => x.Visible) > 1)
            {
                ShowInfoCard(_currentPlayer.Name, Color.White, 1);
            }
        }

        private void EndGame(bool showWinner)
        {
            if (showWinner && _players.Where(x => x.Visible).Any(x => x.Score > 0) && _players.Count(x => x.Visible) > 1)
            {
                MessageBox.Show(String.Format(Resources.PexesoGameEnded, GetWinner()), Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }

            pexesoBoard.Visible = false;

            _gameHasStarted = false;
        }

        private void ShowInfoCard(string text, Color backColor, int interval = 0)
        {
            pexesoBoard.ShowInfo(text, backColor, interval);
        }

        private void GeneratePlayers()
        {
            int idx;
            int playersCount = PlayersCount;

            for (idx = _players.Count; idx < playersCount; idx++)
            {
                string name = _root.UserSettings.PexesoSettings.UserNames.Count > idx ? _root.UserSettings.PexesoSettings.UserNames[idx] : String.Format(Resources.PlayerDefaultName, idx + 1);

                _players.Add(new PlayerDescriptor { Name = name });
            }

            for (idx = 0; idx < _players.Count; idx++)
            {
                _players[idx].Visible = idx < playersCount;
            }

            int top = 0;
            idx = 0;

            try
            {
                SuspendLayout();

                foreach (PlayerControl control in players.Controls.Cast<PlayerControl>())
                {
                    control.Visible = false;
                }

                foreach (PlayerDescriptor playerDescriptor in _players)
                {
                    PlayerControl control;

                    if (players.Controls.Count > idx)
                    {
                        control = (PlayerControl)players.Controls[idx];
                    }
                    else
                    {
                        control = new PlayerControl { Visible = false };
                        players.Controls.Add(control);

                        _playersControls.Add(playerDescriptor, control);
                    }

                    if (!playerDescriptor.Visible)
                    {
                        continue;
                    }

                    control.PlayerName = playerDescriptor.Name;
                    control.Score = 0;
                    control.Top = top;
                    control.Left = 0;
                    control.Visible = true;

                    // handle changed name
                    PlayerDescriptor descriptor = playerDescriptor;
                    control.PlayerNameChanged += (sender, args) => descriptor.Name = control.PlayerName;

                    top += control.Height;
                    idx++;
                }
            }
            finally
            {
                ResumeLayout();
            }

            _currentPlayer = _players.First();
        }

        private bool ValidateGameSetup()
        {
            if (Dictionary == null)
            {
                MessageBox.Show(Resources.NoDictionarySelection, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);

                return false;
            }

            if (Dictionary.Entries.Count < (BoardSize / 2))
            {
                MessageBox.Show(Resources.PexesoBoardTooBigForDictionary, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);

                return false;
            }

            return true;
        }

        private void AdjustPexesoBoardSize()
        {
            try
            {
                pexesoBoard.SuspendResizeEvents = true;

                int size = Math.Min(boardContainer.Height, boardContainer.Width);
                pexesoBoard.Width = pexesoBoard.Height = size;

                if (size == boardContainer.Height)
                {
                    pexesoBoard.Top = 0;
                    pexesoBoard.Left = (boardContainer.Width - size) / 2;
                }
                else
                {
                    pexesoBoard.Top = (boardContainer.Height - size) / 2;
                    pexesoBoard.Left = 0;
                }
            }
            finally
            {
                pexesoBoard.SuspendResizeEvents = false;
            }
        }

        private void StartGame()
        {
            if (!ValidateGameSetup())
            {
                return;
            }

            _lastDictionary = Dictionary;
            _lastNumberOfPlayers = PlayersCount;
            _lastBoardSize = BoardSize;
            _currentPlayer = null;

            InitializeBoard();
            AdjustPexesoBoardSize();
            ResetScore();

            pexesoBoard.Visible = true;

            SwitchPlayer();

            _gameHasStarted = true;
        }

        private string GetWinner()
        {
            int maxScore = 0;
            foreach (PlayerDescriptor descriptor in _players.Where(x => x.Visible))
            {
                maxScore = Math.Max(maxScore, descriptor.Score);
            }

            StringBuilder winner = new StringBuilder();
            foreach (PlayerDescriptor playerDescriptor in _players.Where(x => x.Visible && x.Score == maxScore))
            {
                if (winner.Length > 0)
                {
                    winner.Append(", ");
                }
                winner.Append(playerDescriptor.Name);
            }

            return winner.ToString();
        }

        private void OnFormClosing(object sender, FormClosingEventArgs e)
        {
            if (_gameHasStarted && MessageBox.Show(Resources.QuitGameQuestion, Resources.MessageBoxCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
            {
                e.Cancel = true;
            }
        }

        private void OnPlayersCountChanged(object sender, EventArgs e)
        {
            if (_ignoreGameSetupChanges)
            {
                return;
            }

            if (_gameHasStarted)
            {
                if (MessageBox.Show(Resources.DiscardCurrentGame, Resources.MessageBoxCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                {
                    try
                    {
                        _ignoreGameSetupChanges = true;

                        PlayersCount = _lastNumberOfPlayers;
                    }
                    finally
                    {
                        _ignoreGameSetupChanges = false;
                    }

                    return;
                }

                EndGame(false);
            }

            GeneratePlayers();
        }

        private void OnBoardSizeChanged(object sender, EventArgs e)
        {
            if (_ignoreGameSetupChanges)
            {
                return;
            }

            if (_gameHasStarted)
            {
                if (MessageBox.Show(Resources.DiscardCurrentGame, Resources.MessageBoxCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                {
                    try
                    {
                        _ignoreGameSetupChanges = true;

                        BoardSize = _lastBoardSize;
                    }
                    finally
                    {
                        _ignoreGameSetupChanges = false;
                    }

                    return;
                }

                EndGame(false);
            }
        }

        private void OnFontSizeChanged(object sender, EventArgs e)
        {
            if (_ignoreGameSetupChanges)
            {
                return;
            }

            pexesoBoard.FontSize = FontSize;
        }

        private void OnSelectedDictionaryChanged(object sender, EventArgs e)
        {
            if (_ignoreGameSetupChanges)
            {
                return;
            }

            if (_gameHasStarted)
            {
                if (MessageBox.Show(Resources.DiscardCurrentGame, Resources.MessageBoxCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                {
                    try
                    {
                        _ignoreGameSetupChanges = true;

                        Dictionary = _lastDictionary;
                    }
                    finally
                    {
                        _ignoreGameSetupChanges = false;
                    }

                    return;
                }

                EndGame(false);
            }
        }

        private void OnResize(object sender, EventArgs e)
        {
            if (_lastWindowState != WindowState)
            {
                AdjustPexesoBoardSize();
            }

            _lastWindowState = WindowState;
        }

        private void OnStartClick(object sender, EventArgs e)
        {
            if (_gameHasStarted && MessageBox.Show(Resources.DiscardCurrentGame, Resources.MessageBoxCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
            {
                return;
            }

            StartGame();
        }

        private void OnFormClosed(object sender, FormClosedEventArgs e)
        {
            _root.UserSettings.PexesoSettings.BoardSize.Value = BoardSize;
            _root.UserSettings.PexesoSettings.Dictionary.Value = Dictionary != null ? Dictionary.Entries.Id : Guid.Empty;
            _root.UserSettings.PexesoSettings.FontSize.Value = FontSize;
            _root.UserSettings.PexesoSettings.HighlightFirstWord.Value = cbHighlight.Checked;
            _root.UserSettings.PexesoSettings.PlayersCount.Value = PlayersCount;
            _root.UserSettings.PexesoSettings.UserNames = new List<string>(_playersControls.Select(x => x.Value.PlayerName));
            _root.UserSettings.CheckIn();

            _root.Suspender.Resume();
        }

        private void OnResizeEnded(object sender, EventArgs e)
        {
            AdjustPexesoBoardSize();
        }

        private void OnHighlightChanged(object sender, EventArgs e)
        {
            if (_ignoreGameSetupChanges)
            {
                return;
            }

            pexesoBoard.HighlightFirstWord = cbHighlight.Checked;
        }
    }
}
